#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include "utiles.h"
#include <dirent.h>
#define false 0
#define true 1
#define descubierto_max 1000
#define BACKLOG 100
#define DIR_CUENTAS "cuentas/"


struct datos_cta{
	char tipo_cta;
	float saldo;
};

struct datos_transaccion{
	int socket;
	char * msj;
};

struct transaccion{
	char * cta_origen;
	char * cta_destino;
	char tipo_transaccion;
	float monto;
};

struct datos_cta * parsear_transaccion_realizada(char * transaccion);
void * procesar_transaccion(void * argumentos);
int get_puerto();
struct transaccion parsear_msj(char * msj);
char * timestamp();
int escuchar_clientes();
char * crear_linea(char tipo_cta, char tipo_op, char * cta_origen,char * cta_destino, float monto, float saldo);
int procesar_int();


int main(void) {

	procesar_int();
	escuchar_clientes();


	return EXIT_SUCCESS;
}

int consultar_saldo(char * cta, int puerto)
{
	char cuenta[20] = DIR_CUENTAS;
	int n;
	struct datos_cta * dc;
	char saldo[20];
	bzero(saldo,19);

	strcat(cuenta, cta);
	FILE * fp;

	fp = fopen(cuenta,"r");
	if (fp == NULL)
	{
		fprintf(stderr,"no se pudo leer el archivo %s", cuenta);
		n = send(puerto,"-1",2,0);
		if (n < 0)
			fprintf(stderr, "ERROR escribiendo en el puerto");
		close(puerto);
		return 0;
	}
	if (flock(fileno(fp), LOCK_SH) != 0 )
	{
		fprintf(stderr,"no se pudo obtener el lock del archivo");
		n = send(puerto,"-1",2,0);
				if (n < 0)
					fprintf(stderr, "ERROR escribiendo en el puerto");
				close(puerto);
		return 0;
	}


	dc = parsear_transaccion_realizada(leer_ultima_linea(fp));
	sprintf(saldo, "%.2f", dc->saldo);
	n = send(puerto,saldo,strlen(saldo)+1,0);
	if (n < 0)
	{
		fprintf(stderr, "ERROR escribiendo en el puerto");
		return 0;
	}
	close(puerto);


	flock(fileno(fp), LOCK_UN);
	fclose(fp);
	return 1;

}

int depositar(char * cta,float monto, int puerto)
{
	char cuenta[100] = "";
	memset(cuenta,'\0',20);
	strcpy(cuenta, DIR_CUENTAS);
	int n;
	struct datos_cta * dc;

	strcat(cuenta, cta);
	FILE * fp;

	fp = fopen(cuenta,"r+");
	if (fp == NULL)
	{
		fprintf(stderr,"no se pudo leer el archivo %s", cuenta);
		n = send(puerto,"-1",2,0);
					if (n < 0)
					{
						fprintf(stderr, "ERROR escribiendo en el puerto");
						close(puerto);
						return 0;
					}
				close(puerto);
				return 0;
		return 0;
	}
	if (flock(fileno(fp), LOCK_EX) != 0 )
	{
		fprintf(stderr,"no se pudo obtener el lock del archivo");
		n = send(puerto,"-1",2,0);
			if (n < 0)
			{
				fprintf(stderr, "ERROR escribiendo en el puerto");
				close(puerto);
				return 0;
			}
		close(puerto);
		return 0;
	}


	dc = parsear_transaccion_realizada(leer_ultima_linea(fp));


	fseek( fp, 0L, SEEK_END);
	fprintf(fp,crear_linea(dc->tipo_cta,'D',cta," ",monto, dc->saldo + monto));

	n = send(puerto,"-0",2,0);
	if (n < 0)
	{
		fprintf(stderr, "ERROR escribiendo en el puerto");
		return 0;
	}


	close(puerto);

	fflush(fp);
	flock(fileno(fp), LOCK_UN);
	fclose(fp);
	return 1;

}

int extraer(char * cta,float monto, int puerto)
{
	char cuenta[20] = DIR_CUENTAS;
	int n;
	struct datos_cta * dc;

	strcat(cuenta, cta);
	FILE * fp;

	fp = fopen(cuenta,"r+");
	if (fp == NULL)
	{
		fprintf(stderr,"no se pudo leer el archivo %s", cuenta);
		n = send(puerto,"-1",2,0);
		if (n < 0)
			fprintf(stderr, "ERROR escribiendo en el puerto");
		close(puerto);
		return 0;
	}
	if (flock(fileno(fp), LOCK_EX) != 0 )
	{
		fprintf(stderr,"no se pudo obtener el lock del archivo");
		n = send(puerto,"-1",2,0);
		if (n < 0)
			fprintf(stderr, "ERROR escribiendo en el puerto");
		close(puerto);
		return 0;
	}


	dc = parsear_transaccion_realizada(leer_ultima_linea(fp));

	if ( ((dc->tipo_cta == 'C') &&  (dc->saldo - monto < descubierto_max)) || ( (dc->tipo_cta == 'A') && ((dc->saldo - monto) < 0)) )
	{

		n = send(puerto,"-3",2,0);
		if (n < 0)
			fprintf(stderr, "ERROR escribiendo en el puerto");
		close(puerto);
		flock(fileno(fp), LOCK_UN);
		fclose(fp);
		return 0;
	}

	fprintf(fp, crear_linea(dc->tipo_cta,'E',cta," ",monto, dc->saldo - monto));


	n = send(puerto,"-0",2,0);
	if (n < 0)
	{
		fprintf(stderr, "ERROR escribiendo en el puerto");
		return 0;
	}
	close(puerto);

	fflush(fp);
	flock(fileno(fp), LOCK_UN);
	fclose(fp);
	return 1;

}

int transferir(char * cta_origen,char * cta_destino,float monto, int puerto)
{
	char cuenta_o[20] = DIR_CUENTAS;
	char cuenta_d[20] = DIR_CUENTAS;
	int n;
	struct datos_cta *dc,*dc2;

	strcat(cuenta_o, cta_origen);
	strcat(cuenta_d, cta_destino);
	FILE * fpo, *fpd;

	/*
	 * Obtengo el lock de la cuenta de origen e intento obtener el lock de la cuenta de destino, si no lo consigo espero un
	 * tiempo y vuelvo a intentar. Al quinto intento devuelvo error indicando que el usuario debe volver a intentar mas adelante
	 * por que el servidor esta ocupado
	 */
	fpo = fopen(cuenta_o,"r+");
	if (fpo == NULL)
	{
		fprintf(stderr,"no se pudo leer el archivo %s\n", cuenta_o);
		n = send(puerto,"-1",2,0);
		if (n < 0)
			fprintf(stderr, "ERROR escribiendo en el puerto");
		close(puerto);
		return 0;
	}
	if (flock(fileno(fpo), LOCK_EX) != 0 )
	{
		fprintf(stderr,"no se pudo obtener el lock del archivo");
		n = send(puerto,"-1",2,0);
		if (n < 0)
			fprintf(stderr, "ERROR escribiendo en el puerto");
		close(puerto);
		fclose(fpo);
		return 0;
	}

	fpd = fopen(cuenta_d,"r+");
		if (fpd == NULL)
		{
			fprintf(stderr,"no se pudo leer el archivo %s", cta_destino);
			n = send(puerto,"-1",2,0);
			if (n < 0)
				fprintf(stderr, "ERROR escribiendo en el puerto");
			close(puerto);
			return 0;
		}

		int i = 1;

		while ( (flock(fileno(fpd), LOCK_NB) != 0 ) && (i <= 5))
		{
			i++;
			usleep(100);
		}

		if ( i == 5 )
		{

			n = send(puerto,"-2",2,0);
			if (n < 0)
				fprintf(stderr, "ERROR escribiendo en el puerto");
			close(puerto);
			flock(fileno(fpo), LOCK_UN);
			fclose(fpo);
			fclose(fpd);
			return 0;
		}


		/*extraigo de la cta de origen */
			dc = parsear_transaccion_realizada(leer_ultima_linea(fpo));
			if ( ( (dc->saldo - monto < descubierto_max) && (dc->tipo_cta == 'C')) || ( (dc->tipo_cta == 'A') && (dc->saldo - monto <= 0)) )
			{
				n = send(puerto,"-3",2,0);
						if (n < 0)
							fprintf(stderr, "ERROR escribiendo en el puerto");
				close(puerto);
				flock(fileno(fpo), LOCK_UN);
				flock(fileno(fpd), LOCK_UN);
				fclose(fpo);
				fclose(fpd);
				return 0;
			}


			fprintf(fpo, crear_linea(dc->tipo_cta,'T',cta_origen,cta_destino,monto, dc->saldo - monto));

		/*deposito en la cuenta de destino */

			dc2 = parsear_transaccion_realizada(leer_ultima_linea(fpd));
			fprintf(fpd, crear_linea(dc2->tipo_cta,'T',cta_origen,cta_destino,monto, dc2->saldo + monto));


	n = send(puerto,"-0",2,0);
	if (n < 0)
	{
		fprintf(stderr, "ERROR escribiendo en el puerto");
		return 0;
	}
	close(puerto);
	fflush(fpo);
	fflush(fpd);

	flock(fileno(fpo), LOCK_UN);
	fclose(fpo);
	flock(fileno(fpd), LOCK_UN);
	fclose(fpd);
	return 1;

}



void * procesar_transaccion(void * argumentos)
{
	struct datos_transaccion * dt = (struct datos_transaccion*)argumentos;
	struct transaccion t;

	t = parsear_msj(dt->msj);

	switch(t.tipo_transaccion)
	{
		case 'D': depositar(t.cta_origen,t.monto,dt->socket); break;
		case 'E': extraer(t.cta_origen,t.monto,dt->socket); break;
		case 'T': transferir(t.cta_origen,t.cta_destino, t.monto,dt->socket);break;
		case 'S': consultar_saldo(t.cta_origen,dt->socket); break;
		default: fprintf(stderr, "%s", "no se reconoce el tipo de operacion"); break;

	}

	return NULL;
}



struct datos_cta * parsear_transaccion_realizada(char * transaccion){

	struct datos_cta * datos_cuenta;
	datos_cuenta = (struct datos_cta*)malloc(sizeof(struct datos_cta));

	datos_cuenta->tipo_cta = transaccion[0];

	datos_cuenta->saldo = strtof(strrchr(transaccion, '|')+1, NULL);

	return datos_cuenta;

}

int get_puerto(){
	int puerto;
	FILE * fp;
	fp = fopen("servidor.conf","r");
		if (fp == NULL)
		{
			printf("no se pudo obtener los datos del puerto");
			exit(1);
		}

	if (flock(fileno(fp), LOCK_SH) != 0 ){
		printf("no se pudo obtener el lock del archivo");
		exit(1);
	}
	puerto = atoi(leer_linea(fp, 1));

	fclose(fp);
	flock(fileno(fp), LOCK_UN);

	return puerto;
}

struct transaccion parsear_msj(char * msj)
{
	struct transaccion transaccion;
	char * p;

	transaccion.cta_origen = strtok_r(msj, "|", &p);
	transaccion.cta_destino = strtok_r(NULL, "|", &p);
	transaccion.tipo_transaccion = strtok_r(NULL, "|", &p)[0];
	transaccion.monto = atof(strtok_r(NULL, "|", &p));

	return transaccion;

}

int escuchar_clientes(){

	   int fd;
	   struct sockaddr_in server;
	   /* para la información de la dirección del servidor */
	   int sin_size;


	   if ((fd=socket(AF_INET, SOCK_STREAM, 0)) == -1 ) {
	      printf("error en socket()\n");
	      exit(-1);
	   }

	   server.sin_family = AF_INET;
	   server.sin_port = htons(get_puerto());

	   server.sin_addr.s_addr = INADDR_ANY;   /* INADDR_ANY coloca la dirección IP automáticamente */

	   bzero(&(server.sin_zero),8);   /* escribimos ceros en el reto de la estructura */

	   if(bind(fd,(struct sockaddr*)&server,
	           sizeof(struct sockaddr))==-1) {
	      printf("error en bind() \n");
	      exit(-1);
	   }

	   if(listen(fd,BACKLOG) == -1) {
	      printf("error en listen()\n");
	      exit(-1);
	   }

	   int i=0;
	   while(1) {
		   system("clear");
		   printf("transacciones atendidas por el servidor :%d\n",i);
		   printf("esperando...\n");
		   int fd2;
		   struct sockaddr_in client;	     /* para la información de la dirección del cliente */
		   sin_size=sizeof(client);

	      if ((fd2 = accept(fd,(struct sockaddr *)&client, (socklen_t*)&sin_size))==-1) {
	         printf("error en accept()\n");
	         exit(-1);
	      }
	      void* buffer;
	      buffer=(void*)malloc(sizeof(char)*256);
	      memset(buffer,'\0',256);
	      recv(fd2,buffer,256,0);

	      /* se crea el thread que contesta al socket
	       * el socket se crea desvinculado (pthread_create_dettached)
	       */
	      pthread_attr_t tattr;
	      pthread_t tid;

	      struct datos_transaccion transaccion;

	      transaccion.socket = fd2;
	      transaccion.msj = (char*)buffer;

	      pthread_attr_init(&tattr);
	      pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);

	      if( pthread_create(&tid, &tattr, &procesar_transaccion, (void*)&transaccion) )
	      {
	      	fprintf(stderr, "no se pudo crear el thread");
	      	return 1;
	      }

	      i++;




	   }

	   return 1;

}

char * crear_linea(char tipo_cta, char tipo_op, char * cta_origen,char * cta_destino, float monto, float saldo){
	char * linea = NULL;
	linea = (char*)malloc(150 * sizeof(char));
	memset(linea, '\0',150);
	sprintf(linea,"%c%c%c%c%c%s%c%s%c%s%c%.2f%c%.2f",'\n',tipo_cta, '|', tipo_op, '|',cta_origen,'|', cta_destino, '|', timestamp(),'|',monto, '|', saldo);

	return linea;


}



void * int_ahorro(void * p){

	DIR *dir;
	FILE * fp;
	float monto;
	float interes_ahorro = 0.01;
	struct datos_cta * dc;
	struct dirent *ent;
	char archivo[15];


	while(1){
		dir = opendir (DIR_CUENTAS);
		if (dir != NULL) {
			  while ((ent = readdir (dir)) != NULL) {
				  	 bzero(archivo,15);
				  	 strcat(archivo,DIR_CUENTAS);
				  	 strcat(archivo,ent->d_name);
					fp = fopen(archivo,"r+");
					if (fp == NULL)
					{
						fprintf(stderr,"no se pudo leer el archivo %s", archivo);

					}else{
						if (flock(fileno(fp), LOCK_EX) != 0 )
						{
							fprintf(stderr,"no se pudo obtener el lock del archivo");

						}else{
							dc = parsear_transaccion_realizada(leer_ultima_linea(fp));

							if (dc->tipo_cta == 'A'){
								monto = dc->saldo * interes_ahorro;
								fprintf(fp, crear_linea(dc->tipo_cta,'I'," "," ",monto, dc->saldo + monto));
							}
							flock(fileno(fp),LOCK_UN);
							fclose(fp);
						}
					}
			  }
			  closedir (dir);
			} else {
			  fprintf(stderr,"no se pudo abrir el directorio de cuentas");
			  return NULL;
			}
		sleep(86400);
	}



	return NULL;
}

void * int_cc(void * p){

	DIR *dir;
	FILE * fp;
	float monto;
	float interes_cc = 0.05;
	struct datos_cta * dc;
	struct dirent *ent;
	char  archivo[15];

	while(1){
		dir = opendir (DIR_CUENTAS);
		if (dir != NULL) {
			  while ((ent = readdir (dir)) != NULL) {
				  	 bzero(archivo,15);
				  	 strcat(archivo,DIR_CUENTAS);
				  	 strcat(archivo,ent->d_name);

					fp = fopen(archivo,"r+");
					if (fp == NULL)
					{
						fprintf(stderr,"no se pudo leer el archivo %s", archivo);

					}else{
						if (flock(fileno(fp), LOCK_EX) != 0 )
						{
							fprintf(stderr,"no se pudo obtener el lock del archivo");
						}else{
							dc = parsear_transaccion_realizada(leer_ultima_linea(fp));

							if ((dc->tipo_cta == 'C') && (dc->saldo < 0)){

								monto = dc->saldo * interes_cc;
								fprintf(fp, crear_linea(dc->tipo_cta,'I'," "," ",monto, dc->saldo + monto));
							}
							flock(fileno(fp),LOCK_UN);
							fclose(fp);
						}
					}
			  }
			  closedir (dir);
			} else {
			  fprintf(stderr,"no se pudo abrir el directorio de cuentas");
			  return NULL;
			}
		sleep(2635200);

	}


	return NULL;
}

int procesar_int(){
	 pthread_attr_t tattr;
	 pthread_t tid;

	pthread_attr_init(&tattr);
	pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);

	if( pthread_create(&tid, &tattr, &int_ahorro, NULL) )
	{
	   	fprintf(stderr, "no se pudo crear el thread interés ahorro");
	   	return 1;
	}
	pthread_attr_t tattr1;
	pthread_t tid1;

	pthread_attr_init(&tattr1);
	pthread_attr_setdetachstate(&tattr1,PTHREAD_CREATE_DETACHED);

	if( pthread_create(&tid1, &tattr1, &int_cc, NULL) )
	{
	   	fprintf(stderr, "no se pudo crear el thread interés crédito");
	   	return 1;
	}

	return 1;

}

